#include "Buffers.h"
#include "Device.h"
#include "Texture.h"
#include "Pipeline.h"


#include <iostream>

namespace vkengine
{
        void Buffers::CreateFramebuffers(Device* device ,Pipeline* pipeline)
        {
            m_SwapChainFramebuffers.resize(device->GetSwapChainImageViews().size());

            for (size_t i = 0; i < device->GetSwapChainImageViews().size(); i++) {
                std::array<VkImageView, 2> attachments  = {
                    device->GetSwapChainImageViews()[i],
                    device->GetDepthImageView()
                };

                VkFramebufferCreateInfo framebufferInfo{};
                framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
                framebufferInfo.renderPass = pipeline->GetRenderPass();
                framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
                framebufferInfo.pAttachments = attachments.data();
                framebufferInfo.width = device->GetSwapChainExtent().width;
                framebufferInfo.height = device->GetSwapChainExtent().height;
                framebufferInfo.layers = 1;

                if (vkCreateFramebuffer(device->GetDevice(), &framebufferInfo, nullptr, &m_SwapChainFramebuffers[i]) != VK_SUCCESS)
                {
                    throw std::runtime_error("failed to create framebuffer!");
                }
            }
        }

        void Buffers::CreateCommandPool(Device* device) 
        {
            QueueFamilyIndices queueFamilyIndices = FindQueueFamilies(device->GetPhysicalDevice(), device->Surface());

            VkCommandPoolCreateInfo poolInfo{};
            poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
            poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
            poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();

            if (vkCreateCommandPool(device->GetDevice(), &poolInfo, nullptr, &m_CommandPool) != VK_SUCCESS) {
                throw std::runtime_error("failed to create command pool!");
            }
        }

        void Buffers::CreateBuffer(
            Device* device,
            VkDeviceSize size,
            VkBufferUsageFlags usage,
            VkMemoryPropertyFlags properties,
            VkBuffer& buffer,
            VkDeviceMemory& bufferMemory)
        {
            m_VertexBuffer.CreateBuffer(device, size, usage, properties, buffer, bufferMemory);

        }
        uint32_t  Buffers::FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties)
        {
           return m_VertexBuffer.FindMemoryType(physicalDevice, typeFilter, properties);
        }
        void  Buffers::CopyBuffer(Device* device, VkCommandPool commandPool, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
        {
            m_VertexBuffer.CopyBuffer(device, commandPool, srcBuffer, dstBuffer, size);
        }

        void Buffers::CreateVertexBuffer(Device* device)
        {
            m_VertexBuffer.CreateVertexBuffer(device,m_CommandPool);
        }

        void Buffers::CreateIndexBuffer(Device* device)
        {
            m_VertexBuffer.CreateIndexBuffer(device, m_CommandPool);
        }

        void Buffers::CreateUniformBuffers(Device* device)
        {
            m_VertexBuffer.CreateUniformBuffers(device);
        }

        void Buffers::CreateDescriptorPool(Device* device)
        {
            m_VertexBuffer.CreateDescriptorPool(device);
        }

        void Buffers::CreateDescriptorSets(Device* device, Pipeline* pipeline,Texture* texture)
        {
            m_VertexBuffer.CreateDescriptorSets(device, pipeline, texture);
        }

        void Buffers::UpdateUniformBuffer(Device* device, uint32_t currentImage)
        {
            m_VertexBuffer.UpdateUniformBuffer(device, currentImage);
        }
        void Buffers::CreateCommandBuffers(VkDevice device) 
        {
            m_CommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
            VkCommandBufferAllocateInfo allocInfo{};

            allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
            allocInfo.commandPool = m_CommandPool;
            allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
            allocInfo.commandBufferCount = (uint32_t)m_CommandBuffers.size();

            if (vkAllocateCommandBuffers(device, &allocInfo,m_CommandBuffers.data()) != VK_SUCCESS) {
                throw std::runtime_error("failed to allocate command buffers!");
            }
        }
        VkCommandBuffer Buffers::BeginSingleTimeCommands(Device* device)
        {
            return m_VertexBuffer.BeginSingleTimeCommands(device, m_CommandPool);
        }
        void Buffers::EndSingleTimeCommands(Device* device, VkCommandBuffer commandBuffer)
        {
            m_VertexBuffer.EndSingleTimeCommands(device, commandBuffer, m_CommandPool);
        }

        void Buffers::RecordCommandBuffer(
            VkCommandBuffer commandbuffer,
            uint32_t imageIndex,
            uint32_t currentFrame,
            Device* device,
            Pipeline* pipeline
        )
        {
            VkCommandBufferBeginInfo beginInfo{};
            beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

            if (vkBeginCommandBuffer(commandbuffer, &beginInfo) != VK_SUCCESS) {
                throw std::runtime_error("failed to begin recording command buffer!");
            }

            VkRenderPassBeginInfo renderPassInfo{};
            renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
            renderPassInfo.renderPass = pipeline->GetRenderPass();
            renderPassInfo.framebuffer = m_SwapChainFramebuffers[imageIndex];
            renderPassInfo.renderArea.offset = { 0, 0 };
            renderPassInfo.renderArea.extent = device->GetSwapChainExtent();

            std::array<VkClearValue, 2> clearValues{};
            clearValues[0].color = { {0.0f, 0.0f, 0.0f, 1.0f} };
            clearValues[1].depthStencil = { 1.0f, 0 };

            renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
            renderPassInfo.pClearValues = clearValues.data();

            vkCmdBeginRenderPass(commandbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

            vkCmdBindPipeline(commandbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetGraphPipeline());


            VkViewport viewport{};
            viewport.x = 0.0f;
            viewport.y = 0.0f;
            viewport.width = (float)device->GetSwapChainExtent().width;
            viewport.height = (float)device->GetSwapChainExtent().height;
            viewport.minDepth = 0.0f;
            viewport.maxDepth = 1.0f;
            vkCmdSetViewport(commandbuffer, 0, 1, &viewport);

            VkRect2D scissor{};
            scissor.offset = { 0, 0 };
            scissor.extent = device->GetSwapChainExtent();
            vkCmdSetScissor(commandbuffer, 0, 1, &scissor);

            VkBuffer vertexBuffers[] = { m_VertexBuffer.GetVertexBuffer() };
            VkDeviceSize offsets[] = { 0 };
            vkCmdBindVertexBuffers(commandbuffer, 0, 1, vertexBuffers, offsets);
            vkCmdBindIndexBuffer(commandbuffer, m_VertexBuffer.GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);

            vkCmdBindDescriptorSets(commandbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetPipelineLayout(), 0, 1, &(m_VertexBuffer.GetDescriptorSets()[currentFrame]), 0, nullptr);
            //vkCmdDraw(commandbuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
            vkCmdDrawIndexed(commandbuffer, static_cast<uint16_t>(indices.size()), 1, 0, 0, 0);
          //  vkCmdDraw(commandbuffer, 3, 1, 0, 0);

            vkCmdEndRenderPass(commandbuffer);

            if (vkEndCommandBuffer(commandbuffer) != VK_SUCCESS) {
                throw std::runtime_error("failed to record command buffer!");
            }
        }

        void Buffers::Cleanup(VkDevice device)
        {
            m_VertexBuffer.Cleanup(device);
            vkDestroyCommandPool(device, m_CommandPool, nullptr);

            /*for (auto framebuffer : m_SwapChainFramebuffers)
            {
                vkDestroyFramebuffer(device, framebuffer, nullptr);
            }*/
        }
}